{-
    Haskell block comment!
    whee!
                    multiple
    lines
-}
-- single line comment

import Data.List (nub, sort)
--to import all EXCEPT nub:
--import Data.List hiding (nub)
{-
This makes it so that if we want to reference Data.Map's filter function, we
have to do Data.Map.filter, whereas just filter still refers to the normal filter:

import qualified Data.Map

to make it so we don't have to type out Data.Map before everything:

import qualified Data.Map as M

now we can just use M.filter
-}


doubleUs x y = doubleMe x + doubleMe y
doubleMe x = x + x

doubleSmallNumber x = if x > 100
                        then x
                        else doubleMe x

doubleSmallNumber' x = (if x > 100 then x else x * 2) + 1

conanO'Brien = "It's a-me, Conan O'Brien!"

boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]

length'' xs = sum [1 | _ <- xs]

removeNonUppercase :: String -> String
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]

addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z

circumference :: Float -> Float
circumference r = 2 * pi * r

circumference' :: Double -> Double
circumference' r = 2 * pi * r

lucky :: (Integral a) => a -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"

sayMe :: (Integral a) => a -> String
sayMe 1 = "One!"
sayMe 2 = "Two!"
sayMe 3 = "Three!"
sayMe 4 = "Four!"
sayMe 5 = "Five!"
sayMe x = "Not between 1 and 5"

factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)

addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

first :: (a, b, c) -> a
first (x, _, _) = x

second :: (a, b, c) -> b
second (_, y, _) = y

third :: (a, b, c) -> c
third (_, _, z) = z

head' :: [a] -> a
head' [] = error "Can't call head on an empty list, dummy!"
head' (x:_) = x

tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y

length' :: (Num b) => [a] -> b
length' [] = 0
length' (_:xs) = 1 + length' xs

sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (x:xs) = x + sum' xs   --recursion!

--the thing before the @ is an alias for the entire expression in the () after it
capital :: String -> String
capital "" = "Empty string, whoops!"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height   -- no = here
    | bmi <= skinny = "You're underweight, you emo, you!"
    | bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"
    | bmi <= fat = "You're fat! Lose some weight, fatty!"
    | otherwise   = "You're a whale, congratulations!"
    where   bmi = weight / height^2
            skinny = 18.5
            normal = 25.0
            fat = 30.0

max' :: (Ord a) => a -> a -> a
max' a b
    | a > b     = a
    | otherwise = b

myCompare :: (Ord a) => a -> a -> Ordering
a `myCompare` b
    | a > b     = GT
    | a == b    = EQ
    | otherwise = LT

initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
    where   (f:_) = firstname
            (l:_) = lastname
{-
--  you can use where to define local helper functions. where can also be nested
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
    where   bmi weight height = weight / height^2
-}
--same function but with a let binding inside the list comprehension instead of a helper with where
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h^2]
--let is visible before the | and after the binding
{-
--to get only fat people BMIs:
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h^2, bmi >= 25.0]
-}


--  let <bindings> in <expression>
cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
    let sideArea = 2 * pi * r * h
        topArea = pi * r^2
    in  sideArea + 2 * topArea
{-
    for an inline let binding, could have done:
    let sideArea = 2 * pi * r * h; topArea = pi * r^2 in sideArea + 2 * topArea
-}

describeList :: [a] -> String
describeList xs = "The list is " ++ case xs of  [] -> "empty."
                                                [x] -> "a singleton list."
                                                xs -> "a longer list."

maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:xs) = max x (maximum' xs)

replicate' :: (Num i, Ord i) => i -> a -> [a]
replicate' n x
    | n <= 0    = []
    | otherwise = x:replicate' (n-1) x

take' :: (Num i, Ord i) => i -> [a] -> [a]
take' n _
    | n <= 0    = []
take' _ []      = []
take' n (x:xs) = x : take' (n-1) xs

reverse' :: [a] -> [a]
reverse' [] = []
reverse' (x:xs) = reverse' xs ++ [x]

repeat' :: a -> [a]
repeat' x = x:repeat' x

zip' :: [a] -> [b] -> [(a, b)]
zip' _ [] = []
zip' [] _ = []
zip' (x:xs) (y:ys) = (x,y):zip' xs ys

elem' :: (Eq a) => a -> [a] -> Bool
elem' a [] = False
elem' a (x:xs)
    | a == x    = True
    | otherwise = a `elem` xs

{-
a sorted list is a list that has all the values smaller than (or equal to) the
head of the list in front (and those values are sorted), then comes the head of
the list in the middle and then come all the values that are bigger than the
head (they're also sorted)
-}
quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
    let smallerSorted = quicksort (filter (<=x) xs) --[a | a <- xs, a <= x]
        biggerSorted = quicksort (filter (>x) xs) --[a | a <- xs, a > x]
    in  smallerSorted ++ [x] ++ biggerSorted

--function currying examples:
multThree :: (Num a) => a -> a -> a -> a
multThree x y z = x * y * z
multTwoWithNine = multThree 9
multWithEighteen = multTwoWithNine 2
compareWithHundred :: (Num a, Ord a) => a -> Ordering
compareWithHundred = compare 100
--can also curry infix functions by sectioning them with ()s and only giving arguments
--on one side
divideByTen :: (Floating a) => a -> a
divideByTen = (/10)
--divideByTen 200 == 200 / 10 == (/10) 200
isUpperAlphanum :: Char -> Bool
isUpperAlphanum = (`elem` ['A'..'Z'])
{-
(-4) means negative 4 so we can't use the - operator this way. Instead, do
(subtract 4)
-}
--and now for one that demonstrates functions taking functions explicitly
applyTwice :: (a -> a) -> a -> a -- the () are required here
applyTwice f x = f (f x)
{-
can do:
ghci> applyTwice (+3) 10
16
ghci> applyTwice (++ " HAHA") "HEY"
"HEY HAHA HAHA"
ghci> applyTwice ("HAHA " ++) "HEY"
"HAHA HAHA HEY"
ghci> applyTwice (multThree 2 2) 9
144
ghci> applyTwice (3:) [1]
[3,3,1]
-}

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
{-
can use for:
ghci> zipWith' (+) [4,2,5,6] [2,6,2,3]
[6,8,7,9]
ghci> zipWith' max [6,3,2,1] [7,3,1,5]
[7,3,2,5]
ghci> zipWith' (++) ["foo ", "bar ", "baz "] ["fighters", "hoppers", "aldrin"]
["foo fighters","bar hoppers","baz aldrin"]
ghci> zipWith' (*) (replicate 5 2) [1..]
[2,4,6,8,10]
ghci> zipWith' (zipWith' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]]
[[3,4,6],[9,20,30],[10,12,12]]
-}

flip' :: (a -> b -> c) -> (b -> a -> c)
{-flip' f = g
    where g x y = f y x-}
--better way:
--flip' f y x = f x y
--or we can use lambdas to make it clear that it's mainly meant to be partially
--applied and passed on to a function like map or filter as a parameter:
flip' f = \x y -> f y x

largestDivisible :: (Integral a) => a
largestDivisible = head (filter p [100000,99999..])
    where p x = x `mod` 3829 == 0

--to find sum of all odd squares smaller than 10K:
--sum (takeWhile (<10000) (filter odd (map (^2) [1..])))
--equivalent:
--sum (takeWhile (<10000) [n^2 | n <- [1..], odd (n^2)])

--whee, Collatz sequences
chain :: (Integral a) => a -> [a]
chain 1 = [1]
chain n
    | even n = n:chain (n `div` 2)
    | odd n = n:chain (n*3 + 1)

numLongChains :: Int
{-numLongChains = length (filter isLong (map chain [1..100]))
    where isLong xs = length xs > 15-}
--let's do that with a lambda instead:
numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))

{-
map (+3) [1, 6, 3, 2] == map (\x -> x + 3) [1, 6, 3, 2]
don't use lambdas unnecessarily when currying and partial application are more readable
-}
--"foo a = bar b a" == "foo = bar b"

--implementing sum and elem with list folding:
sum'' :: (Num a) => [a] -> a
--sum'' xs = foldl (\acc x -> acc + x) 0 xs
--equivalently but more readably:
--sum'' = foldl (+) 0
--or we can use foldl1/foldr1 which act like foldl/foldr but assume the first
--element is the starting value:
sum'' = foldl1 (+)
--but foldl1 and foldr1 cause runtime errors if given an empty list, so don't
--use them for things that make sense to call on empty lists

elem'' :: (Eq a) => a -> [a] -> Bool
elem'' y ys = foldl(\acc x -> if x == y then True else acc) False ys

--map function with right fold
map' :: (a->b) -> [a] -> [b]
map' f xs = foldr (\x acc -> f x : acc) [] xs

{-
Folds can be used to implement any function where you traverse a list once,
element by element, and then return something based on that.
Whenever you want to traverse a list to return something, chances are you want a fold.
-}

maximum'' :: (Ord a) => [a] -> a
maximum'' = foldr1(\x acc -> if x > acc then x else acc)

reverse'' :: [a] -> [a]
reverse'' = foldl (\acc x -> x : acc) []
--could also write as:
--foldl(flip(:)) []

product' :: (Num a) => [a] -> a
product' = foldr1 (*)

filter' :: (a -> Bool) -> [a] -> [a]
filter' p = foldr (\x acc -> if p x then x : acc else acc) []

head'' :: [a] -> a --better implemented by pattern matching, but can use folds if want
head'' = foldr1 (\x _ -> x)

last' :: [a] -> a
last' = foldl1 (\_ x -> x)

{-
scanl and scanr are like foldl and foldr, only they report all the intermediate
accumulator states in the form of a list. There are also scanl1 and scanr1,
which are analogous to foldl1 and foldr1.
ghci> scanl (+) 0 [3,5,2,1]
[0,3,8,10,11]
ghci> scanr (+) 0 [3,5,2,1]
[11,8,3,1,0]
ghci> scanl1 (\acc x -> if x > acc then x else acc) [3,4,5,3,7,9,2,1]
[3,4,5,5,7,9,9,9]
ghci> scanl (flip (:)) [] [3,2,1]
[[],[3],[2,3],[1,2,3]]
-}

--How many elements does it take for the sum of the roots of all natural numbers to exceed 1000?
sqrtSums :: Int
sqrtSums = length (takeWhile (<1000) (scanl1 (+) (map sqrt [1..]))) + 1

{-
($) :: (a -> b) -> a -> b
f $ x = f x
Whereas normal function application (putting a space between two things) has a
really high precedence, the $ function has the lowest precedence.
Function application with a space is left-associative (so f a b c is the same as
((f a) b) c)), function application with $ is right-associative.
you can imagine a $ being sort of the equivalent of writing an opening
parentheses and then writing a closing one on the far right side of the expression.

besides getting rid of parentheses, can also use to treat function application as a function:
ghci> map ($ 3) [(4+), (10*), (^2), sqrt]
[7.0,30.0,9.0,1.7320508075688772]

-}

{-
function composition
in math, (f.g)(x) = f(g(x))
pretty much the same here:

(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)

negate . (* 3) returns a function that takes a number, multiplies it by 3 and then negates it.

if we want to turn all numbers in a list negative
ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]
can be rewritten as:
ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24]

If you want to rewrite an expression with a lot of parentheses by using function
composition, you can start by putting the last parameter of the innermost function
after a $ and then just composing all the other function calls, writing them
without their last parameter and putting dots between them. If you have replicate
100 (product (map (*3) (zipWith max [1,2,3,4,5] [4,5,6,7,8]))), you can write it
as replicate 100 . product . map (*3) . zipWith max [1,2,3,4,5] $ [4,5,6,7,8].

fn x = ceiling (negate (tan (cos (max 50 x))))
is equivalent to
fn = ceiling . negate . tan . cos . max 50
-}

oddSquareSum :: Integer
--oddSquareSum = sum (takeWHile (<10000) (filter odd (map (^2) [1..])))
--with function composition:
--oddSquareSum = sum . takeWhile (<10000) . filter odd . map (^2) $ [1..]
--but to make it readable:
oddSquareSum =
    let oddSquares = filter odd $ map (^2) [1..]
        belowLimit = takeWhile (<10000) oddSquares
    in sum belowLimit

numUniques :: (Eq a) => [a] -> Int
numUniques = length . nub

{-
nub, delete, union, intersect and union generic counterparts:
nubBy, deletyBy, etc.
group
is same as
groupBy (==)
-}

{-
import Data.Map as Map
ghci> Map.empty
fromList []

ghci> Map.insert 3 100 Map.empty
fromList [(3,100)]

ghci> Map.null Map.empty
True
ghci> Map.null $ Map.fromList [(2,3),(5,5)]
False

ghci> Map.singleton 3 9
fromList [(3,9)]


-}




































